/***
 * undo-redo设计思路，2022-02-01于春节 by kevin-huang
 * 1) URDO全面负责编辑器的撤销重做实现，编辑器实例只调用相关API(生成撤销)，将URDO与编辑器实现分离
 * 2）以一级段落为撤销-重做的记录及恢复元素（清除API除外），浮动文本域也是以其一级段落为准
 * 3）监听编辑器的execute执行入口，切入撤销记录（部分API不可以直接调研生成撤销，如清空、格式刷，此类API需要在适当的地方调用生成撤销）
 * 4）撤销记录需要记录段落的HTML文本元素，及其对应的选区Region信息，及对应的处理响应API（如不指定响应API则做execomm逻辑）
 * 5）除清空等特殊API外，以当前光标所在段落为提取记录内容的区域
 * 6）为适配退格键的连续删除动作，需要对压入记录采用定时器实现，避免连续删除操作出现冗余的撤销记录
 * by kevin.huang
 * ***/
class URDO extends $B.BaseControl {
    constructor(editor) {
       super();
       this.editIns = editor;
       this.undoStack = new Stack(editCfg.undoSize);
       this.redoStack = new Stack(editCfg.undoSize);
       this.$undoBtn = this.editIns.toolIns.btnElMap["undoFn"];
       this.$redoBtn = this.editIns.toolIns.btnElMap["redoFn"];
    }
    /**
     * 响应按钮undo
     */
    undoFn(){
        console.log("exe undo....");
        let data = this.undoStack.pop();
        this.exeFn(data,()=>{
            this.exeUndo(data);
        });
    }
    /**
     * 响应按钮redo
     */
    redoFn(){
        console.log("exe redo....");
        let data = this.redoStack.pop();
        this.exeFn(data,()=>{
            this.exeRedo(data);
        });
    }
    /**
     * 优先执行非默认处理
     * **/
    exeFn(data,exeCallFn){
        let action = data.action;
        if(this[action]){
            this[action](data);
        }else{
            exeCallFn();
        }
    }
    /**
     * 清空重做记录
     */
    clearRedoRec(){
        this.redoStack.clear();
        this.reativeUI();
    }
    /**
     * 创建一个重做记录
     * **/
    makeRedo(fn,params){
        this.redoData = this.makeData(fn);
    }
    /**
     * 创建一个撤销记录    
     */
    makeUndo(fn,params){  
        if(!this.undoData){            
            this.undoData = this.makeData(fn,params);
        }
    } 
    /**
     * 将当前重做记录放入重做栈
     * **/
    putCurRedo(redoData){
        let rData = redoData ? redoData : this.redoData;
        if(rData){
            this.redoStack.push(rData);
            this.reativeUI();
            this.redoData = undefined;
        }
    }
    /**
     * 创建一个记录
     * fn可以是一个字符串也可以是一个段落元素
     */   
    makeData(fn,params){
        let bakHtml = [],
            action,
            region,
            isStr = typeof fn === "string";
        if(!isStr && $B.Dom.isElement(fn)){
            let pid = fn.id;
            let firstEl = fn.firstChild;
            while(firstEl){
                if($B.Dom.hasClass(firstEl,"p_span")){
                    break;
                }
                firstEl = firstEl.firstChild;
            }
            let spanId = firstEl.id;
            region =  {
                "isCollapsed": true,
                "startPel": pid,
                "endPel": pid,
                "startSpan": spanId,
                "endSpan": spanId,
                "startIdx": 1,
                "endIdx": 1,
                "dirStrtId": pid,
                "dirEndId": pid
            };
            bakHtml.push(fn.outerHTML);
        }else{
            region =  this.copyRegion(); 
            if(isStr && fn === "clearFn"){ //全部清空
                bakHtml.push(this.editIns.$input.innerHTML);
                action = 'recovAll';
            }else if(isStr && fn === "recovAll"){
                action = 'clearFn';
            }else if(region){
                let $p = $B.Dom.children(this.editIns.$input,"#" + region.startPel);
                if(region.startPel != region.endPel){                
                    let $p1 = $B.Dom.children(this.editIns.$input,"#" + region.endPel);
                    while($p){
                        bakHtml.push($p.outerHTML);
                        if($p === $p1){
                            break;
                        }
                        $p =  $p.nextSibling;
                    }                
                }else{
                    bakHtml.push($p.outerHTML);
                }
            }
        }      
        return  {
            action: action,
            bakHtml:bakHtml,
            region: region    
        };
    }
    copyRegion(){
        try{
            let region =  this.editIns.getRegion();
            return {
                "isCollapsed": region.isCollapsed,
                "startPel": region.startPel,
                "endPel": region.endPel,
                "startSpan": region.startSpan,
                "endSpan": region.endSpan,
                "startIdx": region.startIdx,
                "endIdx": region.endIdx,
                "dirStrtId": region.dirStrtId,
                "dirEndId": region.dirEndId
            };
        }catch(e){
            console.log(e);
            return;
        }       
    }
    /**
     * 将当前撤销记录放入栈中
     */
    putCurUndo(undoData){
        let uData = undoData ? undoData :this.undoData;
        if(uData){           
            this.undoStack.push(uData);
            this.reativeUI();
            this.undoData = undefined;
        }
    }
    /**
     * 联动撤销，重做UI按钮
     */
    reativeUI(){
        if(this.undoStack.length > 0){
            $B.Dom.removeClass(this.$undoBtn,"k_edit_tools_disabled");
        }else{
            $B.Dom.addClass(this.$undoBtn,"k_edit_tools_disabled");
        }
        if(this.redoStack.length > 0){
            $B.Dom.removeClass(this.$redoBtn,"k_edit_tools_disabled");
        }else{
            $B.Dom.addClass(this.$redoBtn,"k_edit_tools_disabled");
        }
    }
    /**
     * 恢复所有(清空的撤销)
     * **/
    recovAll(data){
        let bakHtml = data.bakHtml;
        this.editIns.$input.innerHTML = bakHtml.join("");
        if(data.region){
            this.editIns.rebuildRegion(data.region);
        }       
        this.makeRedo("recovAll");
        this.putCurRedo();
        this.reBindEvents(this.editIns.$input);
    }
    /**
     * 执行清空
     */
    clearFn(){
        this.editIns._clearFn();
    }
    /**
     * 执行通用撤销 
     */
    exeUndo(data){
        console.log("执行通用撤销>>>>>>>>>yt>>",data);      
        let rdData = this.execomm(data);
        this.putCurRedo(rdData);
    }
    /**
     * 执行通用重做
     */
    exeRedo(data){
        console.log("执行通用重做>>>>>>>>>>>",data);
        let rdData = this.execomm(data);
        this.putCurUndo(rdData);
    }
    /**
     * 通用执行
     * **/
    execomm(data){
        let region = this.copyRegion();
        let bakHtml = data.bakHtml;
        let $input = this.editIns.$input;
        let saveHtml = [],$preEl;
        let reg = /^k_\d+/;
        for(let i =0 ;i < bakHtml.length ;i++){
            let html = bakHtml[i];
            if(reg.test(html)){
                let rmEl =  $B.Dom.children($input,"#"+html);
                saveHtml.push(rmEl.outerHTML);
                $B.Dom.remove(rmEl);
                continue;
            }
            let $el = $B.Dom.createEl(html);
            let style = $B.Dom.attr($el,"style");
            let id = $el.id;
            let replaceEl = $B.Dom.children($input,"#"+id);
            let isNew = false;
            if(replaceEl){
                saveHtml.push(replaceEl.outerHTML);
                replaceEl.innerHTML= $el.innerHTML ;
            }else{                
                replaceEl = $el;
                isNew = true;
                saveHtml.push(id);
            }
            $B.Dom.attr(replaceEl,{"style":style});
            if(isNew){
                $B.Dom.after($preEl,replaceEl);
            }
            $preEl = replaceEl;
            this.reBindEvents(replaceEl);
        }
        let rdData = { 
            bakHtml:saveHtml,
            region: region 
        };
        try{
            if(data.region){
                this.editIns.rebuildRegion(data.region);
            }            
        }catch(e){
            console.log(e);
            console.log(data);
        }
        return rdData;
    }
    reBindEvents(el){
       this.editIns.tableIns.bindEventsExt(el);
    }
}
